#pragma once

#include "arp_context.hpp"
#include "limbo/details/traits.hpp"
#include "limbo/errc.h"
#include "limbo/ethernet/ethernet_frame.hpp"
#include "limbo/ethernet/state.hpp"
#include "limbo/result.h"
#include "operation.h"
#include <cstdint>
#include <cstring>

namespace limbo::arp {

namespace impl {
static uint8_t eth_ip_prefix[] = {
    0x00, 0x01, /* hardware type, ethernet */
    0x08, 0x00, /* protocol type, ip */
    0x06,       /* hardware length, 6 */
    0x04,       /* protocol length, 4 */
};

template <typename State> constexpr uint16_t get_size() {
  if constexpr (details::has_lower_state_v<State>) {
    using LowerState = typename State::LowerState;
    using F = ethernet::EthernetFrame<LowerState>;
    return F::MIN_SZ - (F::HEADER_SZ + F::TRAILER_SZ);
  } else {
    return 28;
  }
}

} // namespace impl
template <typename State> struct ArpPacket {
  using LowerState = typename State::LowerState;
  using Context = ArpContext<State>;

  uint16_t operation;
  ethernet::MacAddress sender_mac;
  ethernet::MacAddress target_mac;
  ip::IPv4Address sender_ip;
  ip::IPv4Address target_ip;

  inline static uint32_t prefix(const Context &) { return 0; }

  static Result send(State &state, const Context &ctx, Chunk buff,
                     const Chunk) {
    constexpr auto SZ = impl::get_size<State>();
    if (buff.size() < SZ) {
      auto code = (uint32_t)Errc::no_enough_space;
      return Result::make_error(code, &state);
    }

    auto start = (unsigned char *)buff.data();
    auto ptr = start;

    memcpy(ptr, impl::eth_ip_prefix, sizeof(impl::eth_ip_prefix));
    ptr += sizeof(impl::eth_ip_prefix);

    *ptr++ = 0;
    *ptr++ = (uint8_t)ctx.operation;

    memcpy(ptr, ctx.mac_source.bytes, 6);
    ptr += 6;

    memcpy(ptr, &ctx.ip_source.net_value, 4);
    ptr += 4;

    memcpy(ptr, ctx.mac_target.bytes, 6);
    ptr += 6;

    memcpy(ptr, &ctx.ip_target.net_value, 4);
    ptr += 4;

    auto consumed = Chunk(start, SZ);
    return Result::make_consumed(&state, consumed);
  }

  Result recv(Chunk buff, State &state, LowerState *) {
    constexpr auto SZ = impl::get_size<State>();
    if (buff.size() < SZ) {
      auto code = (uint32_t)Errc::arp_malformed;
      return Result::make_error(code, &state, buff);
    }

    auto ptr = (unsigned char *)buff.data();

    auto ok = !memcmp(impl::eth_ip_prefix, ptr, sizeof(impl::eth_ip_prefix));
    if (!ok) {
      auto code = (uint32_t)Errc::arp_unsupported;
      return Result::make_error(code, &state, buff);
    }
    ptr += sizeof(impl::eth_ip_prefix);

    uint16_t op;
    memcpy(&op, ptr, sizeof(op));
    operation = order::big_to_native(op);

    ok = (operation == static_cast<uint16_t>(Operation::request)) ||
         (operation == static_cast<uint16_t>(Operation::response));
    if (!ok) {
      auto code = (uint32_t)Errc::arp_malformed;
      return Result::make_error(code, &state, buff);
    }
    ptr += sizeof(operation);

    mempcpy(sender_mac.bytes, ptr, 6);
    ptr += 6;
    mempcpy(&sender_ip.net_value, ptr, 4);
    ptr += 4;
    mempcpy(target_mac.bytes, ptr, 6);
    ptr += 6;
    mempcpy(&target_ip.net_value, ptr, 4);
    /* ptr += 4; */

    return Result::make_consumed(&state, buff);
  }
};

template <typename State>
inline static uint32_t get_prefix(const ArpContext<State> &) {
  return 0;
}

} // namespace limbo::arp
